MechWarrior 4 AI Brain ABL Tutorial

The following is a walk trough of the MechWarrior 4 Vengeance Brain ABL script of Urban 2, Rescue Pilots, where the player must travel to different nav points until the proper garage where the pilots are hiding is found.  The Tutorial is broken up into actual ABL script; denoted by the bolded font, and the definitions and explanations of the ABL script, denoted with the italicized text.  ABL is an FSM, which stands for Finite State Machine.  This means that the code in any script is organized into states.  Each time the game goes through a script, it only goes through the current, active state.  For example; the script below, things are going to happen in the following order for this script...

1.   The system will define the constant, types and variables.
2.   Values will be set for the variables in the init function.
3.   The system will go to the Startstate.  ALL scripts must have a startstate as the system always goes there immediately after all the constants and variables are defined.
4.	From the Startstate the system will go to either the followpathstate (If the system determines that this unit should be in the mission) or the Limbo state (if the system has determined that this unit should not be in the mission)

This is a slightly modified script for Enemy ‘Mechs from the Rescue Pilots mission in the Urban operation in Mechwarrior IV.  It does the following things...

1.  If the player has set the difficulty to ‘easy’ AND the unit has it's "byebye" variable set to true then the unit self destructs at the beginning of the mission so that there are less units for the player to fight.
2.  It makes the ‘Mechs follows a specific path.
3.  If it finds an enemy unit AND that unit is within it's withdrawrange then it will attack that unit.
4.  If the player has found the garage with the lost pilots in it the unit will (after a specific delay) make a beeline towards that garage.

NOTE: The fsm name below MUST be changed in order for the script to be considered a unique entity!  Each script in a mission MUST have a unique identifier for the system to tell them apart.  That unique identifier is the fsm name below.

fsm Patrolrespond : integer;

//------------------------------------------------------------------
//     Constants
//------------------------------------------------------------------

	const
	
This allows the brain to access a list of constants to make coding easier.  Most of these constants are listed in ABL_orders.doc.  This line must be in all unit brains as many functions use these constants.
		#include_ <content\ABLScripts\mwconst.abi>

//------------------------------------------------------------------
//     Types
//------------------------------------------------------------------

	type

This allows the brain to use different variable types. Again, this line must be in all unit brains for them to work.

		#include_ <content\ABLScripts\mwtype.abi>
	
//------------------------------------------------------------------
//     Variables
//------------------------------------------------------------------

Telling the system you are about to define variables
	var
	
This is a listing of all non-eternal variables that the unit brain will be using.  Note that eternal variables can be set ONLY in the mission brain even though they can be accessed by all scripts in that mission.
	
At what range do I start shooting?
		static integer			attackRange;

 At what range do I withdraw from combat entirely?
		static integer			withdrawRange;

My path
		static integer			path; 

How I move along the path
		static integer			moveType; 

Can I leave the path if attacked? (TRUE by default)
		static boolean			canLeavePath; 

Piloting skill
		static integer			piloting; 

Gunnery skill/chance to hit
		static integer			gunnery; 

Minimum amount of time I will wait between shots once a weapon is reloaded
		static real			minDelay;

Maximum amount of time I will wait between shots once a weapon is reloaded
		static real			maxDelay; 

Elite level.  This helps determine tactics, defensive manuevers, opportunity fire and other things.  It indicates a general level of combat experience.
		static integer 			eliteLevel; 

My attack throttle
		static integer			attackThrottle;

 How far away from me will I detect an enemy shot?
 		static integer			isShotRadius;	

What kind of enemies to look for
		static integer			findTypes; 

The attack sound I play when I first attack
		static integer 			attackSound; 

The sound I play when I die
		static integer 			deathSound; 

My default mood.		
		static integer			mood; 

The speed at which I move along the path
		static integer			speed;

My take-off distance if I'm an airplane (ignored if not an airplane)
		static integer			takeOffDistance; 

what tactic do I use in combat
		static integer			mytactic;					

how long will I wait once the pilots have been found before I run to the garage where they have been found
		static integer			mytime;
					
Am I one of the units who is supposed to suicide if the mission is easy difficulty		
		static boolean			byebye;

Is the mission easy difficulty?						 
		static integer			wimpy;

		
//------------------------------------------------------------------
//     Init: my initialization function
//------------------------------------------------------------------

function Init;
	code

Script-specific variables.  Note that many of these variables will have their values set twice in this script.  This is because default values are set then those values are changed depending on WHICH unit is running the script. In the actual mission about 10 different units shared this script.  The setting of different values for the variables allowed them to do this while they exhibited different behavior to the player.

The average unit in this mission will attack any enemy unit that comes within 450 meters of it
		attackRange		= 450;

Default path value.  A value of -1 means that there is no path for the unit to follow.  This will be changed below for specific units
		path			= -1; 

Default movetype.  Follow the path in a circle, other movetypes are listed in ABL_Orders.rtf
		moveType		= move_circle;

The units commanded by this brain are allowed to leave their path while in combat.
		canLeavePath	= true;

Move at 600 kilometers per hour while following a path.  In other words go as fast as you can.
		speed			= 600; 

If you are an airplane you will travel 150 meters on the ground before taking off.
		takeOffDistance	= 150; 

Default tactic = allow the AI to find the best one for the situation															
		mytactic = TACTIC_PICK_BEST;

The amount of time to wait before responding to the pilots being found is a random number between 10 and 75.		
		mytime = rand (10,75);

Driver settings, below are the default combat skills for the units using this brain.
		piloting		= 80;
		gunnery		= 92;
		minDelay		= 0.1;
		maxDelay		= 0.4;
		eliteLevel		= 87;
		attackThrottle	= 80;
		isShotRadius		= 70;
		attackSound		= -1; // no sound
		deathSound		= -1; // no sound
		mood			= NEUTRAL_START;
		findTypes		= FT_DEFAULT;
		
The default unit with this brain will NOT suicide if the player is playing at easy difficulty
		byebye = false;
		
This finds out the difficulty level and sets the variable "wimpy" to it's value.
		wimpy = getdifficulty;

Here is where the units get individualized.  The getmemoryinteger command checks a memory cell for each unit.  This memory cell is set in the editor and can be different for each unit.  Therefore we can set variables to different values depending on what the memory cell has been set to.  This time we are checking memory cell 9 for the unit.	
	switch (getmemoryinteger(me,9))

The following will be set for that unit if that unit’s memory cell 9 is set to 1, 
	case 1:

Change values for the below variables.  This unit will be following pathone, he will be moving in a seesaw fashion rather than a circular fashion, he will be moving VERY slow and he will suicide if the mission is played at easy difficulty.
		path 		= epa_pathone;
		moveType	= move_seesaw;
		speed 		= 10;
		byebye 	= true;
 	endcase;
	
If memory cell 9 is set to 2
	case 2:
	
Then follow pathtwo.
		path 		= epa_pathtwo;

Travel at a crawl
		speed 		= 5;

Crank it up to full speed in combat
		attackthrottle = 100;

Try to get behind your opponent in combat
		mytactic 	= TACTIC_REAR;

Suicide if mission is played at easy difficulty
		byebye 	= true;
 	endcase;

If memory cell 9 is set to 3
	case 3:

Follow path name: paththree
		path 		= epa_paththree;

Travel that path at 15 kilometers per hour
		speed 		= 15;

Go to full speed in combat
		attackthrottle = 100;

Respond to the pilots being found faster than average
		mytime 	= rand (5,25);
 	endcase;

If memory cell 9 is set to 4
	case 4:

Travel along path four
		path 		= epa_pathfour;

Move in a seesaw fashion along that path
		moveType	= move_seesaw;

Move along path a 20 kilometers per hour
		speed 		= 20;

Have a lower attack range than average
		Attackrange 	= 350;

Fight in an agressive fashion
		mood 		= agressive_start;
 	endcase;

If memory cell 9 is set to 5
	case 5:

Travel along path name: pathfive
		path 		= epa_pathfive;

Move at 25 kilometers per hour
		speed 		= 25;

Fight in an aggressive fashion
		mood 		= agressive_start;
 	endcase;

If memory cell 9 is set to 6
	case 6:
Travel along path name: pathsix
		path 		= epa_pathsix;

Move at 30 kilometers per hour
		speed 		= 30;

Fight in a jousting fashion, charging in then turning around and charging in again.
		mytactic 	= TACTIC_JOUST;

Fight at full throttle
		attackthrottle = 100;
 	endcase;

We are done with the switch now.
	endswitch;
	
Withdrawrange is set to twice attackrange.  This command is below all the others in this function because the switch above sets the attackrange to different values for some units.
		withdrawRange	= attackrange * 2;

We are done setting variable values
endfunction;

//------------------------------------------------------------------
//	StartState: my initial state
//------------------------------------------------------------------

state StartState;

	code
	
Now that we have determined WHAT the default combat skills are for each unit we need to actually run the combat skill commands so that the system knows what those skill levels are.
		SetFiringDelay		(ME,minDelay,maxDelay);
		SetIsShotRadius		(ME,isShotRadius);
		SetEntropyMood		(ME,mood);
		SetCurMood			(ME,mood);
		SetSkillLevel			(ME,piloting,gunnery,eliteLevel);
		SetAttackThrottle		(ME,attackThrottle);

If I am shot by a friendly unit ignore it.  This keeps AI units from attacking each other if they accidentally shoot each other		
		SetIgnoreFriendlyFire	(ME,true);

 If I am one of the units who is supposed to suicide in an easy difficulty mission 	
		if (byebye) then

AND it's an easy difficulty mission then…
			If (wimpy == EASY_DIFFICULTY) then
…shutdown…
				shutdown(me);
…teleport myself off the map…
				teleporttohell(me);
…self destruct…
				selfdestruct(me);
…trans to a state where nothing happens, just in case	
				trans limbo;
			endif;
		endif;

If I am a plane and I have taken off (NOTE:  This command automatically returns true if the unit is not a plane) then…
		if (orderTakeOff(takeOffDistance) == true) then
…go to the followpathstate.
			trans FollowPathState;
		endif;

End the startstate
endstate;
 
Limbostate.  I go here ONLY if I am supposed to do nothing.
state limbo;
	code
	
endstate;

//------------------------------------------------------------------
//	FollowPathState: follow our path, but keep an eye out for enemies
//------------------------------------------------------------------
state FollowPathState;
	code
	
Find me a target.  This function does numerous things...
1.  It looks for any enemy units within my attackrange
2.  It looks to see if I (or any other units in my radiosquad) have been shot.
3.  If either of these things happed it sets my target to the unit found.

If I have found a target, then…
		if (FindEnemy(attackRange,findTypes)) then

If that target is within my withdrawrange then…
			if (iswithin(me,(gettarget(me)),withdrawrange))	then

…go to the attackstate.
				trans AttackState;
			endif;
		endif;

If I have a path to follow then, if path does not equal –1, then…
		if (path <> -1) then

Follow it at whatever speed I am supposed to move and whatever movetype I am supposed to use
			OrderMoveResumePatrol(path,speed,moveType,true,false);

If I DONT have a path to follow, and path equals –1…
		else

Stand still and look around.
			OrderMoveLookOut;
		endif;
		
If the player has found the garage then

NOTE: The foundgarage variable is an eternal boolean set in the mission script.  This brain does not set it, it simply checks it.  The section of code in the mission script that sets this
variable ALSO starts gti_timer_5 running so the system can easily check how long it has been since the pilots have been found.
		if (foundgarage) then
		
The below ‘If statement’ checks the timer coded in the mission ABL script, I have waited long enough then…
			if(timegreater(gti_timer_5,mytime)) then

The below line clears the current order from the AI
				clearmoveorder(me);

After the current move order is cleared, the AI is given the state that directs it to the objective garrage
				trans gettogarage;
			endif;
		endif;
		
All stats need an endstate
endstate;

//--------------------------------------------------------
// gettogarage state: Move towards the garage where the pilots are
//--------------------------------------------------------
state gettogarage;
	code

Find me a target.  This function does numerous things...
1.  It looks for any enemy units within my attackrange
2.  It looks to see if I (or any other units in my radiosquad) have been shot.
3.  If either of these things happed it sets my target to the unit found.

If I have found a target then
		if (FindEnemy(attackRange,findTypes)) then

If that target is within my withdrawrange then
			if (iswithin(me,(gettarget(me)),withdrawrange))	then

Go to the attackstate.
				trans AttackState;
			endif;
		endif;
		
Move to a navpoint near the garage as fast as I can go.  NOTE: This navpoint is an eternal variable set in the mission script.
		ordermovetoobject(navpoint,600);	
	endstate;

//------------------------------------------------------------------
//	AttackState: attack my current target, or return to path if done
//------------------------------------------------------------------
state AttackState;
	code
	
Determine if I should leave the attack state.  This returns true if ANY of the following events occur
1.  The unit I have been fighting has gotten outside my withdrawrange
2.  The unit I have been fighting is now dead.
3.  The unit I have been fighting has ceased to exist for any reason whatsoever.

If any of the above are true then	
		if (LeaveAttackState(withdrawRange)) then
		
Stop fighting
			OrderStopAttacking;

Clear my targets
			SetTarget(ME,NO_UNIT);

If the pilots have not been found then
			if (not foundgarage) then

Go back to the followpathstate
				trans FollowPathState;
Otherwise
			Else

Go back to the gettogarage state
				trans gettogarage;
			endif; 

Attack my target using whatever tactic I have set
		OrderAttackTactic(mytactic,canLeavePath);

End the state
		endif;
endstate;

//------------------------------------------------------------------
//	DeadState: OK, I kicked the bucket.
//this state is reached ONLY when the unit has actually been destroyed
//------------------------------------------------------------------

state DeadState;
	code

  Kills Unit	
	orderDie;

endstate;

End the script
endfsm.